﻿using System;
using System.Activities;
using System.Activities.Presentation;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Markup;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;

namespace Vision.Core.ActivityComponents.Common
{
    /// <summary>
    /// 
    /// </summary>    
    [Designer(typeof(Vision.Core.ActivityComponents.Common.ForEachDesigner))]
    [ContentProperty("Body")]
    public class ForEach : NativeActivity
    {
        Variable<IEnumerator> valueEnumerator;
        CompletionCallback onChildComplete;
                
        /// <summary>
        /// 
        /// </summary>
        [RequiredArgument]
        [DefaultValue(null)]
        public InArgument<IEnumerable> Values
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        [Browsable(false)]
        [DefaultValue(null)]
        [DependsOn("Values")]
        public ActivityAction<object> Body
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        CompletionCallback OnChildComplete
        {
            get
            {
                if (this.onChildComplete == null)
                {
                    this.onChildComplete = new CompletionCallback(GetStateAndExecute);
                }

                return this.onChildComplete;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public ForEach(): base()
        {
            this.valueEnumerator = new Variable<IEnumerator>();
            this.Body = new ActivityAction<object>(){
                Argument = new DelegateInArgument<object>() { Name = "item" }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="metadata"></param>
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            RuntimeArgument valuesArgument = new RuntimeArgument("Values", typeof(IEnumerable), ArgumentDirection.In, true);
            metadata.Bind(this.Values, valuesArgument);
            metadata.AddArgument(valuesArgument);
            metadata.AddDelegate(this.Body);
            metadata.AddImplementationVariable(this.valueEnumerator);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        protected override void Execute(NativeActivityContext context)
        {
            IEnumerable values = this.Values.Get(context);
            if (values == null){
                throw new InvalidOperationException(string.Format("ForEach requires a non null Values argument ({0})", this.DisplayName));                
            }

            IEnumerator valueEnumerator = values.GetEnumerator();
            this.valueEnumerator.Set(context, valueEnumerator);

            if (this.Body == null || this.Body.Handler == null){
                while (valueEnumerator.MoveNext()){
                    // do nothing
                };
                OnForEachComplete(valueEnumerator);
                return;
            }
            InternalExecute(context, valueEnumerator);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="completedInstance"></param>
        void GetStateAndExecute(NativeActivityContext context, ActivityInstance completedInstance)
        {
            IEnumerator valueEnumerator = this.valueEnumerator.Get(context);            
            InternalExecute(context, valueEnumerator);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="valueEnumerator"></param>
        void InternalExecute(NativeActivityContext context, IEnumerator valueEnumerator)
        {        
            if (!valueEnumerator.MoveNext()){
                OnForEachComplete(valueEnumerator);
                return;
            }

            // After making sure there is another value, let's check for cancelation
            if (context.IsCancellationRequested){
                context.MarkCanceled();
                OnForEachComplete(valueEnumerator);
                return;
            }

            context.ScheduleAction(this.Body, valueEnumerator.Current, this.OnChildComplete);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="valueEnumerator"></param>
        void OnForEachComplete(IEnumerator valueEnumerator)
        {
            IDisposable disposable = (valueEnumerator as IDisposable);
            if (disposable != null){
                disposable.Dispose();
            }
        }
    }
    
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Designer(typeof(Vision.Core.ActivityComponents.Common.ForEachDesigner))]
    [ContentProperty("Body")]
    public class ForEach<T> : NativeActivity
    {
        Variable<IEnumerator> valueEnumerator;
        CompletionCallback onChildComplete;
    
        /// <summary>
        /// 
        /// </summary>
        public ForEach(): base()
        {
            this.valueEnumerator = new Variable<IEnumerator>();
            this.Body = new ActivityAction<T>(){
                Argument = new DelegateInArgument<T>() { Name = "item" }
            };                        
        }

        /// <summary>
        /// 
        /// </summary>
        [RequiredArgument]
        [DefaultValue(null)]
        public InArgument<IEnumerable> Values { get; set; }

        /// <summary>
        /// 
        /// </summary>
        [Browsable(false)]
        [DefaultValue(null)]
        [DependsOn("Values")]
        public ActivityAction<T> Body { get; set; }

        /// <summary>
        /// 
        /// </summary>
        private CompletionCallback OnChildComplete
        {
            get{
                if (this.onChildComplete == null){
                    this.onChildComplete = new CompletionCallback(GetStateAndExecute);
                }
                return this.onChildComplete;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="metadata"></param>
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            RuntimeArgument valuesArgument = new RuntimeArgument("Values", typeof(IEnumerable), ArgumentDirection.In, true);
            metadata.Bind(this.Values, valuesArgument);
            metadata.AddArgument(valuesArgument);
            metadata.AddDelegate(this.Body);
            metadata.AddImplementationVariable(this.valueEnumerator);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        protected override void Execute(NativeActivityContext context)
        {
            IEnumerable values = this.Values.Get(context);
            if (values == null){
                throw new InvalidOperationException(string.Format("ForEach requires a non null Values argument ({0})", this.DisplayName));
            }

            IEnumerator valueEnumerator = values.GetEnumerator();
            this.valueEnumerator.Set(context, valueEnumerator);

            if (this.Body == null || this.Body.Handler == null){
                while (valueEnumerator.MoveNext()){
                    // do nothing
                };
                OnForEachComplete(valueEnumerator);
                return;
            }
            InternalExecute(context, valueEnumerator);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="completedInstance"></param>
        private void GetStateAndExecute(NativeActivityContext context, ActivityInstance completedInstance)
        {
            IEnumerator valueEnumerator = this.valueEnumerator.Get(context);
            InternalExecute(context, valueEnumerator);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="valueEnumerator"></param>
        private void InternalExecute(NativeActivityContext context, IEnumerator valueEnumerator)
        {
            if (!valueEnumerator.MoveNext()){
                OnForEachComplete(valueEnumerator);
                return;
            }

            // After making sure there is another value, let's check for cancelation
            if (context.IsCancellationRequested){
                context.MarkCanceled();
                OnForEachComplete(valueEnumerator);
                return;
            }

            context.ScheduleAction(this.Body, (T)valueEnumerator.Current, this.OnChildComplete);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="valueEnumerator"></param>
        private void OnForEachComplete(IEnumerator valueEnumerator)
        {
            IDisposable disposable = (valueEnumerator as IDisposable);
            if (disposable != null){
                disposable.Dispose();
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Designer(typeof(Vision.Core.ActivityComponents.Common.ForEachDesigner))]
    [ContentProperty("Body")]
    public class ParallelForEach<T> : NativeActivity
    {
        bool hasCompleted;                
        IEnumerator valueEnumerator;        
        CompletionCallback onBodyComplete;

        /// <summary>
        /// 
        /// </summary>
        [RequiredArgument]
        [DefaultValue(null)]
        public InArgument<IEnumerable> Values { get; set; }

        [DependsOn("Values")]        
        public InArgument<int> MaxConcurrentBranches { get; set; }

        /// <summary>
        /// 
        /// </summary>
        [Browsable(false)]
        [DefaultValue(null)]
        [DependsOn("Values")]
        public ActivityAction<T> Body { get; set; }
        
        /// <summary>
        /// 
        /// </summary>
        public ParallelForEach() : base()
        {            
            this.Body = new ActivityAction<T>(){
                Argument = new DelegateInArgument<T>() { Name = "item" }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="metadata"></param>
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            // add the arguments to the argument collection
            metadata.AddArgument(new RuntimeArgument("Values", typeof(IEnumerable), ArgumentDirection.In, true));
            metadata.AddArgument(new RuntimeArgument("MaxConcurrentBranches", typeof(int), ArgumentDirection.In));
                        
            // add the body to the delegates collection
            metadata.AddDelegate(this.Body);            
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        protected override void Execute(NativeActivityContext context)
        {
            IEnumerable values = this.Values.Get(context);
            if (values == null){
                throw new InvalidOperationException(string.Format("ForEach requires a non null Values argument ({0})", this.DisplayName));
            }

            valueEnumerator = values.GetEnumerator();

            // initialize the values for creating the execution window (max and runningCount)
            int max = this.MaxConcurrentBranches.Get(context);
            
            if (max < 1) { 
                max = int.MaxValue; 
            }
                        
            int runningCount = 0;

            // initialize the value of the completion variable
            this.hasCompleted = false;
            
            // cache the completion callback
            onBodyComplete = new CompletionCallback(OnBodyComplete);

            // iterate while there are items available and we didn't exceed the throttle factor
            while (runningCount < max && valueEnumerator.MoveNext()){
                // increase the running instances counter
                runningCount++;
                if (this.Body != null){
                    context.ScheduleAction(this.Body, (T)valueEnumerator.Current, onBodyComplete);
                }
            }            
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="completedInstance"></param>
        private void OnBodyComplete(NativeActivityContext context, ActivityInstance completedInstance)
        {
            // for the completion condition, we handle cancelation ourselves
            if (!this.hasCompleted){
                if (completedInstance.State != ActivityInstanceState.Closed && context.IsCancellationRequested){
                    // If we hadn't completed before getting canceled
                    // or one of our iteration of body cancels then we'll consider
                    // ourself canceled.
                    context.MarkCanceled();
                    this.hasCompleted = true;                    
                }
                else{
                    // get the next child and schedule it!                    
                    if (this.valueEnumerator.MoveNext()){
                        context.ScheduleAction(this.Body, (T)this.valueEnumerator.Current, onBodyComplete);
                    }
                }
            }            
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="completedInstance"></param>
        /// <param name="result"></param>
        private void OnConditionComplete(NativeActivityContext context, ActivityInstance completedInstance, bool result)
        {
            if (result){
                context.CancelChildren();
                this.hasCompleted = true;                
            }
            else{
                // get the next child and schedule it!                    
                if (this.valueEnumerator.MoveNext()){
                    context.ScheduleAction(this.Body, (T)this.valueEnumerator.Current, onBodyComplete);
                }
            }
        }
    }
}
